#include "common.h"
#include "memory/cache.h"
#include "burst.h"
#include "stdlib.h"
#include <time.h>

L1 cache_L1[CACHE_L1_SET_NUM * CACHE_L1_WAY_NUM];
L2 cache_L2[CACHE_L2_SET_NUM * CACHE_L2_WAY_NUM];

void init_cache()
{
    for (int i = 0; i < CACHE_L1_SET_NUM * CACHE_L1_WAY_NUM; i++)
        cache_L1[i].valid = false;
    for (int i = 0; i < CACHE_L2_SET_NUM * CACHE_L2_WAY_NUM; i++)
    {
        cache_L2[i].dirty_val = false;
        cache_L2[i].valid = false;
    }
    return;
}

int read_cache_L1(hwaddr_t addr)
{
    uint32_t setIndex = ((addr >> CACHE_BLOCK_BIT) & (CACHE_L1_SET_NUM - 1));
    uint32_t tag = (addr >> (CACHE_BLOCK_BIT + CACHE_L1_SET_BIT));

    int wayIndex;
    int whole_begin_wayIndex = setIndex * CACHE_L1_WAY_NUM;
    int whole_end_wayIndex = (setIndex + 1) * CACHE_L1_WAY_NUM;
    for (wayIndex = whole_begin_wayIndex; wayIndex < whole_end_wayIndex; wayIndex++)
        if (cache_L1[wayIndex].valid && cache_L1[wayIndex].tag == tag)
            return wayIndex;

    srand(time(0));
    int wayIndex_L2 = read_cache_L2(addr);
    wayIndex = whole_begin_wayIndex + rand() % CACHE_L1_WAY_NUM;
    memcpy(cache_L1[wayIndex].data, cache_L2[wayIndex_L2].data, CACHE_BLOCK_SIZE);

    cache_L1[wayIndex].valid = true;
    cache_L1[wayIndex].tag = tag;
    return wayIndex;
}

int read_cache_L2(hwaddr_t addr)
{
    uint32_t setIndex = ((addr >> CACHE_BLOCK_BIT) & (CACHE_L2_SET_NUM - 1));
    uint32_t tag = (addr >> (CACHE_BLOCK_BIT + CACHE_L2_SET_BIT));
    uint32_t block_start = ((addr >> CACHE_BLOCK_BIT) << CACHE_BLOCK_BIT);

    int wayIndex;
    int whole_begin_wayIndex = setIndex * CACHE_L2_WAY_NUM;
    int whole_end_wayIndex = (setIndex + 1) * CACHE_L2_WAY_NUM;
    for (wayIndex = whole_begin_wayIndex; wayIndex < whole_end_wayIndex; wayIndex++)
        if (cache_L2[wayIndex].valid && cache_L2[wayIndex].tag == tag)
            return wayIndex;

    srand(time(0));
    wayIndex = whole_begin_wayIndex + rand() % CACHE_L2_WAY_NUM;
    if (cache_L2[wayIndex].valid && cache_L2[wayIndex].dirty_val)
    {
        uint8_t tmp[BURST_LEN << 1];
        memset(tmp, 1, sizeof(tmp));
        uint32_t block_start_x = (cache_L2[wayIndex].tag << (CACHE_L2_SET_BIT + CACHE_BLOCK_BIT)) | (setIndex << CACHE_BLOCK_BIT);
        for (int i = 0; i < CACHE_BLOCK_SIZE / BURST_LEN; i++)
            call_ddr3_write(block_start_x + BURST_LEN * i, cache_L2[wayIndex].data + BURST_LEN * i, tmp);
    }
    for (int i = 0; i < CACHE_BLOCK_SIZE / BURST_LEN; i++)
        call_ddr3_read(block_start + BURST_LEN * i, cache_L2[wayIndex].data + BURST_LEN * i);
    cache_L2[wayIndex].valid = true;
    cache_L2[wayIndex].dirty_val = false;
    cache_L2[wayIndex].tag = tag;
    return wayIndex;
}

void write_cache_L1(hwaddr_t addr, size_t len, uint32_t data)
{
    uint32_t setIndex = ((addr >> CACHE_BLOCK_BIT) & (CACHE_L1_SET_NUM - 1));
    uint32_t tag = (addr >> (CACHE_BLOCK_BIT + CACHE_L1_SET_BIT));
    uint32_t block_bias = addr & (CACHE_BLOCK_SIZE - 1);
    int wayIndex;
    int whole_begin_wayIndex = setIndex * CACHE_L1_WAY_NUM;
    int whole_end_wayIndex = (setIndex + 1) * CACHE_L1_WAY_NUM;
    for (wayIndex = whole_begin_wayIndex; wayIndex < whole_end_wayIndex; wayIndex++)
    {
        if (cache_L1[wayIndex].valid && cache_L1[wayIndex].tag == tag)
        {
            if (block_bias + len > CACHE_BLOCK_SIZE)
            {
                dram_write(addr, CACHE_BLOCK_SIZE - block_bias, data);
                memcpy(cache_L1[wayIndex].data + block_bias, &data, CACHE_BLOCK_SIZE - block_bias);
                write_cache_L2(addr, CACHE_BLOCK_SIZE - block_bias, data);
                write_cache_L1(addr + CACHE_BLOCK_SIZE - block_bias,
                               len - (CACHE_BLOCK_SIZE - block_bias),
                               data >> (CACHE_BLOCK_SIZE - block_bias));
            }
            else
            {
                dram_write(addr, len, data);
                memcpy(cache_L1[wayIndex].data + block_bias, &data, len);
                write_cache_L2(addr, len, data);
            }
            return;
        }
    }
    write_cache_L2(addr, len, data);
    return;
}

void write_cache_L2(hwaddr_t addr, size_t len, uint32_t data)
{
    uint32_t setIndex = ((addr >> CACHE_BLOCK_BIT) & (CACHE_L2_SET_NUM - 1));
    uint32_t tag = (addr >> (CACHE_BLOCK_BIT + CACHE_L2_SET_BIT));
    uint32_t block_bias = addr & (CACHE_BLOCK_SIZE - 1);
    int wayIndex;
    int whole_begin_wayIndex = setIndex * CACHE_L2_WAY_NUM;
    int whole_end_wayIndex = (setIndex + 1) * CACHE_L2_WAY_NUM;
    for (wayIndex = whole_begin_wayIndex; wayIndex < whole_end_wayIndex; wayIndex++)
    {
        if (cache_L2[wayIndex].valid && cache_L2[wayIndex].tag == tag)
        {
            cache_L2[wayIndex].dirty_val = true;
            if (block_bias + len > CACHE_BLOCK_SIZE)
            {
                memcpy(cache_L2[wayIndex].data + block_bias, &data, CACHE_BLOCK_SIZE - block_bias);
                write_cache_L2(addr + CACHE_BLOCK_SIZE - block_bias,
                               len - (CACHE_BLOCK_SIZE - block_bias),
                               data >> (CACHE_BLOCK_SIZE - block_bias));
            }
            else
                memcpy(cache_L2[wayIndex].data + block_bias, &data, len);
            return;
        }
    }
    wayIndex = read_cache_L2(addr);
    cache_L2[wayIndex].dirty_val = true;
    memcpy(cache_L2[wayIndex].data + block_bias, &data, len);
    return;
}
